/** * Copyright (c) 2002-2011 "Neo Technology," * Network Engine for Objects in Lund AB [http://neotechnology.com] * * This file is part of Neo4j. * * Neo4j is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ package org.neo4j.bench.chart; import java.awt.Color; import java.awt.Dimension; import java.io.BufferedReader; import java.io.File; import java.io.FileReader; import java.io.IOException; import java.text.DecimalFormat; import java.util.ArrayList; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Set; import java.util.SortedSet; import java.util.TreeSet; import org.jfree.chart.ChartUtilities; import org.jfree.chart.JFreeChart; import org.jfree.chart.axis.CategoryAxis; import org.jfree.chart.axis.NumberAxis; import org.jfree.chart.labels.ItemLabelAnchor; import org.jfree.chart.labels.ItemLabelPosition; import org.jfree.chart.labels.StandardCategoryItemLabelGenerator; import org.jfree.chart.plot.CategoryPlot; import org.jfree.chart.plot.PlotOrientation; import org.jfree.chart.renderer.category.BarRenderer3D; import org.jfree.data.category.DefaultCategoryDataset; import org.jfree.ui.TextAnchor; import org.neo4j.bench.cases.mixedload.Stats; public class GenerateOpsPerSecChart { private static final int TESTS_TO_DRAW = 7; public static final String OPS_PER_SECOND_FILE_ARG = "ops-per-sec-file"; public static final String CHART_FILE_ARG = "chart-file"; private final String inputFilename; private final String outputFilename; private boolean performanceHasDegraded; // If performance has degraded, store the stats that trumped current performance here. private Stats trumpingStats; private final SortedSet<Stats> data; private final double threshold; private boolean hasProcessed = false; public GenerateOpsPerSecChart( String inputFilename, String outputFilename, double threshold ) { this.inputFilename = inputFilename; this.outputFilename = outputFilename; this.threshold = threshold; data = loadOpsPerSecond( this.inputFilename ); } public void process() throws Exception { trumpingStats = detectDegradation( threshold ); performanceHasDegraded = trumpingStats != null; hasProcessed = true; } public boolean performanceHasDegraded() { if(hasProcessed) return performanceHasDegraded; throw new RuntimeException("Unable to determine performance degradation before having run #process()"); } public Stats getTrumpingStats() { return trumpingStats; } public Stats getLatestStats() { return data.last(); } public void generateChart() throws Exception { if(!hasProcessed) throw new RuntimeException("Unable to generate chart before having run #process()"); DefaultCategoryDataset dataset = generateDataset(); BarRenderer3D barRenderer = new BarRenderer3D(); barRenderer.setBaseItemLabelsVisible( true ); barRenderer.setBaseItemLabelGenerator( new StandardCategoryItemLabelGenerator( "{2}", new DecimalFormat( "###.#" ) ) ); barRenderer.setBasePositiveItemLabelPosition( new ItemLabelPosition( ItemLabelAnchor.OUTSIDE12, TextAnchor.TOP_CENTER ) ); barRenderer.setItemMargin( 0.06 ); CategoryAxis catAxis = new CategoryAxis( "Bench Case" ); CategoryPlot basePlot = new CategoryPlot( dataset, catAxis, new NumberAxis( "Operations Per Sec" ), barRenderer ); basePlot.setOrientation( PlotOrientation.VERTICAL ); basePlot.setDataset( dataset ); basePlot.getRangeAxis().setLowerBound( 0 ); JFreeChart chart = new JFreeChart( "Performance Chart", basePlot ); Dimension dimensions = new Dimension( 1600, 900 ); File chartFile = new File( outputFilename ); if ( performanceHasDegraded ) { chart.setBackgroundPaint( Color.RED ); } System.out.println("Saving chart to " + chartFile.getAbsolutePath()); ChartUtilities.saveChartAsPNG( chartFile, chart, (int) dimensions.getWidth(), (int) dimensions.getHeight() ); } private Stats detectDegradation( double threshold ) { Stats latestRun = getLatestStats(); for ( Stats previous : data.headSet( latestRun ) ) { double previousReads = previous.getAvgReadsPerSec(); double previousWrites = previous.getAvgWritePerSec(); if ( previousReads > latestRun.getAvgReadsPerSec() * ( 1 + threshold ) || previousWrites > latestRun.getAvgWritePerSec() * ( 1 + threshold ) ) { return previous; } } return null; } private DefaultCategoryDataset generateDataset() { SortedSet<Stats> dataToDraw = new TreeSet<Stats>(); if ( data.size() > TESTS_TO_DRAW ) { Iterator<Stats> it = data.iterator(); int i = 0; while ( data.size() - i++ > TESTS_TO_DRAW ) { Stats toSkip = it.next(); if(toSkip == trumpingStats) { // Always include any stats that are currently // trumping our latest stats. dataToDraw.add(toSkip); } } dataToDraw.addAll(data.tailSet( it.next() )); } else { dataToDraw.addAll(data); } DefaultCategoryDataset dataSet = new DefaultCategoryDataset(); for ( Stats key : dataToDraw ) { dataSet.addValue(key.getAvgReadsPerSec(), key.getName(), "avg reads"); dataSet.addValue(key.getAvgWritePerSec(), key.getName(), "avg writes"); dataSet.addValue(key.getPeakReadsPerSec(), key.getName(), "peak reads"); dataSet.addValue(key.getPeakWritesPerSec(), key.getName(), "peak writes"); dataSet.addValue(key.getSustainedReadsPerSec(), key.getName(), "sust reads"); dataSet.addValue(key.getSustainedWritesPerSec(), key.getName(), "sust writes"); } return dataSet; } /** * Opens the operations per second file, reads in the contents and creates a * SortedSet of the therein stored Stats. */ public static SortedSet<Stats> loadOpsPerSecond( String fileName ) { File dataFile = new File( fileName ); if ( !dataFile.exists() ) { return null; } BufferedReader reader = null; SortedSet<Stats> result = new TreeSet<Stats>(); Stats currentStat; try { reader = new BufferedReader( new FileReader( dataFile ) ); String line; // The current line while ( ( line = reader.readLine() ) != null ) { currentStat = Stats.parse( line ); if ( currentStat != null ) { result.add( currentStat ); } } } catch ( IOException e ) { // This should not happen as we check above e.printStackTrace(); return null; } finally { if ( reader != null ) { try { reader.close(); } catch ( IOException e ) { e.printStackTrace(); } } } return result; } }